fix(daemon): scope pairwise a2a transcript reads to the session's own agent - #969
Conversation
… agent (#967) Every postless toAgent child of one caller shares the synthetic a2a:<caller> channel + the caller's thread, so all pairwise sessions of one caller land in ONE physical transcript thread — and the §8.5 catch-up and turn-context refresh, built for genuinely shared conversations, replayed SIBLINGS' private deliveries (role assignments, canaries included) and reports into each child's prompts. Measured in the webchat Werewolf real run (#941). The sessions are pairwise; the reads now are too. isSyntheticA2aChannel (cp-collab-routes, beside the coordinate minting) marks the synthetic channel; the store gains transcriptSince(Revision)ForAgent — the same sender/recipient/transcript_recipient delivery scope the console session views already use, factored into one shared predicate — and the three context seams (§8.5 catch-up in session-manager, the thread-context refresh, localInvalidatingEvents) route through the scoped reads exactly when the session sits on a synthetic a2a channel. Every a2a writer already stamps recipient, so a pair's own rows are unaffected. Ordinary shared conversations (channels, webchat rosters, co-hosted participants) are untouched. Pinned by packages/daemon/test/a2a-transcript-privacy.test.ts (red without the fix: the sibling's canary appears in the child's prompt) and, at game level, by the new canaryCrossVisibility=0 audit in the scripted webchat Werewolf gate (every player prompt scanned across conversation AND pairwise sessions). Closes #967. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Approved at d1a87a7d1f43e8d18bc278a6b0199988a208ba7d. I found no blocking regression in this revision.
The change applies the existing per-agent delivery predicate consistently to the synthetic a2a: session’s initial catch-up, turn-context refresh, and late local invalidation fence, while leaving genuinely shared conversations on the existing unscoped path. Directed rows remain visible through recipient/transcript_recipient, and each child’s own output remains visible through sender. The focused privacy test and Werewolf canary audit cover sibling delivery and reply leakage across subsequent prompts.
Verification performed: exact synthetic-merge parents matched the trusted base/head; all changed TypeScript files parsed and matched repository formatting; the factored SQL predicate was smoke-tested for sender, direct-recipient, delivery-table, and peer-internal-row behavior. I could not run the Vitest suite because this isolated workspace has no installed dependencies.
sent by review-bot (Codex · gpt-5.6-sol) · open in session
What
Fixes #967. Every postless
toAgentchild of one caller shares the synthetica2a:<caller>channel + the caller's thread, so all of a caller's pairwise sessions land in one physical transcript thread — and the §8.5 catch-up and turn-context refresh, built for genuinely shared conversations, replayed siblings' private deliveries (role assignments, canaries included) and reports into each child's prompts. Measured in the webchat Werewolf real run (#941): a player's night prompt carried the referee's role deliveries to other players.The niche fix
The sessions are pairwise; the reads now are too — the coordinate scheme, session keys, wire, and every genuinely shared conversation are untouched:
isSyntheticA2aChannel(cp-collab-routes, beside the coordinate minting; the transcript channel key is prefix-preserving so it works on both raw and keyed forms);transcriptSinceForAgent/transcriptSinceRevisionForAgent— the same sender/recipient/transcript_recipientdelivery scope the console session views already use, factored into one sharedAGENT_DELIVERY_SCOPE_SQL(the three existing inline copies now reuse it, byte-identical SQL);scopeReadsToAgent), andlocalInvalidatingEvents.Every a2a writer already stamps
recipient(trigger append, observed-inbound, coalesce append), so a pair's own rows are all still visible to it.Tests
packages/daemon/test/a2a-transcript-privacy.test.ts— two children of one caller, two rounds of private deliveries + replies: each child sees its own canary, never the sibling's delivery or reply, across every prompt of every turn. Red without the fix (the sibling's canary appears verbatim in the child's second-turn catch-up), green with it.canaryCrossVisibilitypinned0in both CI games. This is the live-topology regression probe: role secrecy in the single-conversation game now actually holds.eval:collab:contracts119,eval:parity23, full typecheck + lint.Expectations were only extended (the two new pins); nothing weakened.
🤖 Generated with Claude Code